﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using fess.Screens;
using fess.Maps;

namespace fess.Graphics
{
  interface IDrawable
  {
    bool Draw(SpriteBatch batch, Rectangle view, TimeSpan time);
  }

  class WalkingCharacter : IDrawable
  {
    const double attackTime = 0.1;

    SpriteSheet sheet;
    protected Vector2 position = Vector2.Zero, direction = Vector2.UnitX, lastStepPos = Vector2.Zero, lastDrawn = Vector2.Zero;
    double stepSizeHoriz = 3, stepSizeVert = 2;
    int[] sides = { 2, 0, 1, 3 }, steps = { 1, 2, 1, 0 };
    int step = 0;

    enum State { Walking, Fighting, Dead };
    State currentState = State.Walking;
    int frameToShow = 1;
    TimeSpan lastFrame = TimeSpan.Zero;
    bool deadRight;
    float deadDepth;

    bool remove = false;

    public virtual Vector2 Position
    {
      get { return position; }
      set 
      {
        if (value != position)
        {
          direction = value - position;
          var dir = GetDirection();
          position = value;
          var stepSize = (dir == 0 || dir == 2) ? stepSizeHoriz : stepSizeVert;
          if ((lastStepPos - position).Length() > stepSize)
          {
            step = (step + 1) % steps.Length;
            lastStepPos = position;
          }
        }
        else
        {
          Stop();
        }
      }
    }

    private void Stop()
    {
      step = 0;
      lastStepPos = position;
    }

    public WalkingCharacter(SpriteSheet sheet)
    {
      this.sheet = sheet;
      deadDepth = VisResources.GroundMarksDepth - 0.0001f * (float)Misc.Rand.NextDouble() - 0.001f;
    }

    public bool Draw(SpriteBatch batch, Rectangle view, TimeSpan time)
    {
      if (remove)
        return false;

      switch(currentState)
      {
        case State.Walking:
          if (lastDrawn == position)
            Stop();

          DrawAnimationFrame(batch, view, steps[step]);
          lastDrawn = position;
          break;

        case State.Fighting:
          var attackPhase = (time - lastFrame).TotalSeconds / attackTime;
          DrawAnimationFrame(batch, view, frameToShow);
          if (frameToShow == 1)
          {
            if (attackPhase >= 1)
              currentState = State.Walking;
            else
            {
              float t = (float)(1 - attackPhase);
              lastStepPos = position + direction * t / 2;// -Vector2.UnitY * 4 * (t - t * t) * 25;
            }
          }
          else
            if (attackPhase >= 1)
            {
              frameToShow = 1;
              lastFrame = time;
            }
            else
            {
              float t = (float)attackPhase;
              lastStepPos = position + direction * t / 2 - Vector2.UnitY * 4 * (t - t * t) * 5;
            }
          break;

        case State.Dead:
          batch.Draw(sheet.Texture, new Vector2((int)(lastStepPos.X - view.X), (int)(lastStepPos.Y - view.Y)),
            new Rectangle((int)(sheet.SpriteSize.X * (deadRight ? 1.4 : 1)), sheet.SpriteSize.Y * frameToShow,
              (int)(sheet.SpriteSize.X * 0.6), sheet.SpriteSize.Y),
              Color.White, (float)Math.PI / 2 * (deadRight ? -1 : 1), 
              new Vector2(deadRight ? 0 : (int)(sheet.SpriteSize.X * 0.6), sheet.SpriteSize.Y / 2), 1, SpriteEffects.None, deadDepth);
          break;
      }

      return true;
    }

    protected void DrawAnimationFrame(SpriteBatch batch, Rectangle view, int frame)
    {
      int dir = GetDirection();
      int side = sides[dir];
      sheet.Draw(batch, frame, side, lastStepPos, view, 0, GetDepth(view), 1, Color.White);
    }

    private float GetDepth(Rectangle view)
    {
      return -(lastStepPos.Y - view.Y) / view.Height / 1000 + VisResources.CharDepth;
    }

    private int GetDirection()
    {
      var angle = Math.Atan2(direction.Y, direction.X);
      if (angle < 0)
        angle += Math.PI * 2;

      if (Math.Abs(angle - Math.PI / 4) < 0.001 || Math.Abs(angle - 5 * Math.PI / 4) < 0.001)
        angle -= 0.005;
      else if (Math.Abs(angle - 3 * Math.PI / 4) < 0.001 || Math.Abs(angle - 7 * Math.PI / 4) < 0.001)
        angle += 0.005;

      int dir = (int)Math.Floor((angle + Math.PI / 4) / Math.PI * 2) % 4;
      return dir;
    }

    public void Attack(Vector2 what, TimeSpan when)
    {
      direction = what - position;
      frameToShow = Misc.Rand.Next(2) == 0 ? 0 : 2;
      currentState = State.Fighting;
      lastFrame = when;
    }

    public void Die()
    {
      currentState = State.Dead;
      frameToShow = sides[0];
      deadRight = Misc.Rand.Next(2) == 0;
    }

    public void Rise()
    {
      currentState = State.Walking;
    }

    public Vector2 Direction
    {
      get { return direction; }
      set { direction = value; }
    }

    public void Remove()
    {
      remove = true;
    }
  }
}
